/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.core.component.server;

import com.vii.brillien.core.component.SuperPresence;
import com.vii.brillien.core.component.db.DataSource;
import com.vii.brillien.core.component.servlet.BrillienServlet;
import com.vii.brillien.core.component.websocket.BrillienWebSocketApplication;
import com.vii.brillien.core.management.BrillienServices;
import com.vii.brillien.core.management.component.MetaServices;
import com.vii.brillien.core.management.component.structure.Configuration;
import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.ignition.BrillienStatics;
import com.vii.brillien.ignition.classloading.PresenceClassLoader;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.annotations.PresenceService;
import com.vii.brillien.kernel.annotations.lifecycle.Resident;
import com.vii.brillien.kernel.axiom.atomic.Presence;
import com.vii.brillien.kernel.axiom.atomic.PresenceManager;
import com.vii.streamline.annotation.P;
import com.vii.streamline.services.IOServices;
import com.vii.streamline.services.StringServices;
import com.vii.streamline.services.json.JsonServices;
import com.vii.streamline.services.reflection.ReflectionServices;
import com.vii.streamline.structures.collections.InnerList;
import com.vii.streamline.structures.collections.InnerMap;
import com.vii.streamline.webserver.WebServer;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

/**
 * For every freshly loaded component archieve, this presence will load all presences defined by the deployment descriptor
 */
@PresenceService( logLevel = PresenceService.CONFIG )
@Resident
public class PresenceLoader<R> extends SuperPresence<R> {

    protected static final String KEY_PRESENCE      = "presences";
    protected static final String KEY_WEBSOCKETS    = "websockets";
    protected static final String KEY_SERVLETS      = "servlets";

    private boolean canWatch;

    @Override
    public void reset() {
        super.reset();

        try {
            canWatch = BrillienContext.COMPONENT_PUBLISHING_RECIPIENT != null && BrillienServices.getPresenceManagerByName(this,BrillienContext.COMPONENT_PUBLISHING_RECIPIENT) != null;
        } catch (BrillienException e) { e.printStackTrace(); }
    }

    protected InnerList<File> getFiles( File f, InnerList<File> files ){
        if( f.isDirectory() ){
            File[] classes = f.listFiles( new ClassFilter() );
            files.add( classes );

            for( File child : f.listFiles() )
                getFiles( child, files );
        }
        return files;
    }
    
    protected class ClassFilter implements FileFilter {
        @Override
        public boolean accept(File pathname) {
            return pathname.getAbsolutePath().endsWith(".class");
        }
    }

    protected Map<String, List<Class>> getPresencesToActivate( ClassLoader loader, File containerDir ) throws BrillienException {
        Map<String, List<Class>> res = new InnerMap<String, List<Class>>();
        res.put(KEY_PRESENCE, new InnerList<Class>());
        res.put(KEY_WEBSOCKETS, new InnerList<Class>());
        res.put(KEY_SERVLETS, new InnerList<Class>());

        File f = new File( containerDir.getAbsolutePath() + File.separator + BrillienStatics.BIN_DIR );

        if( f.exists() && f.isDirectory() ){
            InnerList<File> classes = getFiles( f , new InnerList<File>() );
            for( File aclass : classes ){

                String className = StringServices.getClassNameByFileReference( f, aclass );

                Class type = null;
                try {
                    type = loader.loadClass( className );
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }

                if( type != null && BrillienContext.SSO_ACTIVATED ){
                    if( BrillienContext.SSO_PROVIDER.equals( className ) ){
                        BrillienServices.loadSSOPresence( type );
                    }
                    else if( BrillienContext.API_KEY_PROVIDER.equals( className ) ){
                        BrillienServices.loadAPI_KEYPresence( type );
                    }
                }


                if(  type != null &&
                        !Modifier.isAbstract(type.getModifiers())
                ){
                    if( type.isAnnotationPresent( PresenceService.class ) || SuperPresence.class.isAssignableFrom( type ) )
                        res.get(KEY_PRESENCE).add( type );

                    else if( BrillienWebSocketApplication.class.isAssignableFrom( type ) )
                        res.get(KEY_WEBSOCKETS).add( type );

                    else if( BrillienServlet.class.isAssignableFrom( type ) )
                        res.get(KEY_SERVLETS).add( type );
                }
            }
        }

        log( "Number of found presences:: " + res.size() );

        return res;
    }

    protected String activatePresences( @P(name = "jarReferenceName") String jarReferenceName ) throws BrillienException {
        log( "Activation phase for :: " + jarReferenceName );        

        final PresenceClassLoader classLoader = MetaServices.getClassLoader(jarReferenceName);

        final String mainClass = MetaServices.getMainClass( jarReferenceName );
        if( mainClass != null ){
            log( "Executing activator's premain method:" + mainClass );
            try{
                Class c = classLoader.loadClass( mainClass );
                if( ReflectionServices.getMethod( c, "premain", String[].class ) != null )
                    ReflectionServices.simpleInvokeMethod(
                            ReflectionServices.getMethod(c, "premain", String[].class),
                            c, new String[][]{new String[]{}}
                    );
            } catch( Exception e ){
                BrillienContext.exceptionLog( PresenceLoader.class.getSimpleName(), "activator premain execution", e );
            }
        }

        List<PresenceManager> managers = MetaServices.getSourceInfo( jarReferenceName );
        if( managers != null )
            for( PresenceManager manager : managers ){
                log( "Activated presence :: " + manager.getPresenceClass().getName() );
                if( manager.getManagementType() == Presence.RESIDENT ){
                    Presence p = manager.getInstance( BrillienServices.SERVICES_FLOW );
                    log( "Instantiating Resident type: " + manager.getName() );
                }
                if( DataSource.class.isAssignableFrom( manager.getPresenceClass() ) ){
                    DataSource ds = BrillienServices.getPresenceByName( BrillienServices.SERVICES_FLOW, manager.getName() );
                    Connection c = ds.getConnection();
                    ds.retrieveConnection( c );
                    log( "Successfull DataSource connection test for : " + manager.getName() );
                }
            }

        if( mainClass != null ){
            log( "Executing activator's main method:" + mainClass );
            try{
                Class c = classLoader.loadClass( mainClass );
                if( ReflectionServices.getMethod( c, "main", String[].class ) != null )
                    ReflectionServices.simpleInvokeMethod(
                            ReflectionServices.getMethod(c, "main", String[].class),
                            c, new String[][]{new String[]{}}
                    );
            } catch( Exception e ){
                BrillienContext.exceptionLog( PresenceLoader.class.getSimpleName(), "activator main execution", e );
            }
        }

        if( canWatch ){
            sendSet( BrillienContext.COMPONENT_PUBLISHING_RECIPIENT,
                    BrillienServices.COMPONENT_LOADED, new InnerMap<String, Object>( BrillienServices.COMPONENT_LOADED_NANE, jarReferenceName )
            );
        }

        log("Activated: " + jarReferenceName);
        System.out.println("Activated: " + jarReferenceName);

        return "OK";
    }

    /**
     * After having the component archieve extracted by the FileWatcher presence, every Presence in the given folder will be loaded by this method
     * @param containerDirPath path to the container directory of the components
     * @return "OK" in case of successful loading
     * @throws BrillienException in any other case
     */
    protected String load( @P(name = "containerDirPath") String containerDirPath, @P(name = "jarReferenceName") String jarReferenceName ) throws BrillienException {
        File containerDir = new File( containerDirPath );

        log( "Loading phase for :: " + jarReferenceName );

        try {
            if( new File( containerDir.getAbsolutePath() + File.separator + BrillienStatics.PRESENCE_CONFIG_FILE ).exists() ){
                Configuration config = JsonServices.<Configuration>parseJSON( IOServices.getStreamAsString(
                        IOServices.getInputStream( containerDir.getAbsolutePath() + File.separator + BrillienStatics.PRESENCE_CONFIG_FILE )
                ), Configuration.class );

                log( "Processing library:: " + jarReferenceName );
                
                MetaServices.registerMetaInfo( jarReferenceName, config );
            }

            final PresenceClassLoader classLoader = new PresenceClassLoader(
                    BrillienContext.primordialClassLoader
            );
            BrillienContext.primordialClassLoader.addChildPresenceClassLoader( classLoader );
            classLoader.appendURL( new File( containerDir.getAbsolutePath() + File.separator + BrillienStatics.BIN_DIR ) );
            classLoader.appendURL( new File( containerDir.getAbsolutePath() + File.separator + BrillienStatics.LIB_DIR ) );

            MetaServices.registerClassloader( jarReferenceName, classLoader );

            Map<String, List<Class>> classes = getPresencesToActivate( classLoader, containerDir );
            log( "Loading presences of " + jarReferenceName );

            for( Class type : classes.get(KEY_PRESENCE) ){

                String PresenceName = BrillienServices.liaison.registerPresence( type );

                PresenceManager manager = BrillienServices.liaison.getPresenceManagerOf( PresenceName );
                manager.setClassLoader( classLoader );
                try{
                    manager.setParameters(MetaServices.getPresenceParameters(jarReferenceName, type.getName()));
                } catch( Exception e ){ e.printStackTrace(); }

                MetaServices.registerSourceInfo( jarReferenceName, manager );

                log( "Successfull loading for:: ", new InnerList<Object>( type.getName() ) );
            }

            Map<String, String> clones = MetaServices.getClonePresences( jarReferenceName );
            for( String name : clones.keySet() ){
                log( "Cloning: " + name + " of " + clones.get(name) );
                BrillienServices.liaison.registerPresence( clones.get(name), name );
            }

            if(BrillienContext.WEBSERVER_ON)
            for( Class type : classes.get(KEY_SERVLETS) ){
                BrillienServlet servletInstance;
                try{
                    servletInstance = ReflectionServices.<BrillienServlet>invokeMethod( "getServletInstance", type );
                } catch (Exception e){ servletInstance = (BrillienServlet)type.newInstance(); }
                if( servletInstance != null )
                WebServer.addHttpService(
                        "/" + servletInstance.getContext(),
                        "/" + servletInstance.getServiceName(),
                        servletInstance,
                        null
                );
            }

            if(BrillienContext.WEBSERVER_WEBSOCKET_ON)
            for( Class type : classes.get(KEY_WEBSOCKETS) ){
                log( "Loading servlet:: " + type.getName() );
                BrillienWebSocketApplication bwsa = ReflectionServices.invokeMethod("getWebSocketApplicationInstance", type);
                String turl = BrillienContext.WEBSERVER_WEBSOCKET_CONTEXT + "/" + bwsa.getDeploymentName();
                bwsa.initWebSocketApplication();
                bwsa.setServiceContext( turl );
                WebServer.addWebSocketApplication(
                        turl, bwsa
                );
                log( "Successfull loading of servlet:: " + type.getName() );
            }

            log(" DescriptionInfo::" + MetaServices.getDescriptionInfo( jarReferenceName ));
            log(" VendorDescription::" + MetaServices.getVendorDescription( jarReferenceName ));
            log(" VendorHomePage::" + MetaServices.getVendorHomePage( jarReferenceName ));
            log(" VendorName::" + MetaServices.getVendorName( jarReferenceName ));
            
        } catch (Exception e) {
            exceptionLog("PresenceClassLoader", "load", e);
            return "Failure";
        }

        log("Loaded: " + jarReferenceName );
        System.out.println("Loaded: " + jarReferenceName );

        return "OK";
    }

}

